using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace TelerikMvcGridCustomBindingHelper.Projections
{
    /// <summary>
    /// This expression visitor will replace an input parameter by another one
    /// 
    /// see http://stackoverflow.com/questions/4601844/expression-tree-copy-or-convert
    /// </summary>
    public class ExtractMembersVisitor : ExpressionVisitor
    {
        // ReSharper disable ReturnTypeCanBeEnumerable.Global
        public ReadOnlyCollection<MemberExpression> MemberExpressions
        {
            get { return new ReadOnlyCollection<MemberExpression>(_memberExpressions); }
        }
        // ReSharper restore ReturnTypeCanBeEnumerable.Global

        // ReSharper disable ReturnTypeCanBeEnumerable.Global
        public ReadOnlyCollection<MemberInfo> MemberInfos
        {
            get { return new ReadOnlyCollection<MemberInfo>(_memberInfos); }
        }
        // ReSharper restore ReturnTypeCanBeEnumerable.Global

        private readonly ParameterExpression _newParameter;
        private readonly ParameterExpression _oldParameter;
        private readonly List<MemberExpression> _memberExpressions;
        private readonly List<MemberInfo> _memberInfos;

        public ExtractMembersVisitor(ParameterExpression newParameter, ParameterExpression oldParameter)
        {
            _memberExpressions = new List<MemberExpression>();
            _memberInfos = new List<MemberInfo>();
            _newParameter = newParameter;
            _oldParameter = oldParameter;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            // replace all old param references with new ones
            return node.Type == _oldParameter.Type ? _newParameter : node;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Expression != _oldParameter) // if instance is not old parameter - do nothing
                return base.VisitMember(node);

            var newObj = Visit(node.Expression);
            if (_memberInfos.FirstOrDefault(info => info.Name == node.Member.Name) == null)
            {
                _memberInfos.Add(_newParameter.Type.GetProperty(node.Member.Name));
                _memberExpressions.Add(Expression.MakeMemberAccess(newObj, _memberInfos.Last()));
            }
            return _memberExpressions.Last();
        }
    }
}